/**
 ******************************************************************************
 * @file	fpga-20c-chip.c
 * @brief	fpga 调试工具
 *
 *
 *
 * @section Depend
 *	-# spi_mxs.ko
 *	-# spidev.ko
 *
 * 2016-10-19,MenglongWu,MenglongWoo@aliyun.com
 */


#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "fpga-20c.h"
#include "proginfo.h"

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

#define STRLEN        (16)

#define DEF_SPI       "/dev/spidev3.0"

static char    *device = DEF_SPI;
static uint32_t speed  = 500000;

struct fpga_dev *dev;

// 是否执行循环测试
static int is_do_loop = 0;  // 目前未用到

static char str_freq[STRLEN] = "800mhz";
static int  freq             = TP_FREQ_800;


// 默认APD电压
static char apd       = TP_APD_39V;
static int  is_do_apd = 0;

// 默认关闭红光
static char red       = OTDR_RL_OFF;
static int  is_do_red = 0;

// 是否检查通讯光检查
static char is_do_comlaser = 0;  // 默认每次进行OTDR测试都检查，相当于测试设备

// amp 1-4 开关表有效值
int sw1_3[] = {
    TP_AMP1_ONLY_S1,
    TP_AMP1_ONLY_S1,
    TP_AMP1_ONLY_S2,
    TP_AMP1_ONLY_S3
};
int sw4[] = {
    TP_AMP2_S4_OFF,
    TP_AMP2_S4_OFF,
    TP_AMP2_S4_OFF,
    TP_AMP2_S4_OFF,
    TP_AMP2_S4_ON
};
/* 以16进制表示 AMP 管脚
 * 0x10 只打开运放开关S1
 * 0x20 只打开运放开关S2
 * 0x30 只打开运放开关S3
 * 0x04 只打开运放开关S4
 * 0xn4 打开S1/S2/S3其中之一与S4，即0x14、0x24、0x34
 */
static int tmp_amp   = 0x10;
static int amp       = TP_AMP1_ONLY_S1;  // 默认只打开开关 S1
static int is_do_amp = 0;

// 累加次数
static char str_addcount[STRLEN] = "10000";
// static long addcount;

static char str_sec[STRLEN] = "15s";   // 描述测量时间
static int  sec             = 60 * 3;  // 默认3min

// 脉冲参数
static char str_dist[STRLEN]  = "60km";  // 描述距离
static char str_pluse[STRLEN] = "40ns";  // 描述脉冲宽度
static long plus;                        // 最终fpga能识别的脉冲

// 启动OTDR测试，具体参数由其他选项指定，-t 选项使用默认参数
static int is_do_otdr = 0;

// 激光器功率，OTDR_LASER_PW_LOW ~ OTDR_LASER_PW_HIGH
static int laser_power       = OTDR_LASER_PW_HIGH;
static int is_do_laser_power = 0;

// 默认波长
static int channel = 1310;


/******************************************************************************
 * 输入提示
 ******************************************************************************/
static void print_usage(const char *prog)
{
    printf("Usage: %s [-DrtpaACw]\n", prog);
    puts("  -D <dev>    device to use (default /dev/spidev3.0)\n"
         // "  -s <speed>  max speed (500000Hz)\n"
         "  -r <red>    red ligth mode (0:off 1:on s:slow f:fast)\n"
         "  -t <test>   use default test param (60km/40ns/15s)\n"
         "  -P <param>  specific OTDR test param ( km/ ns/ s)\n"
         "  -a <amp>    amplifier switch (0x14: S1 on S4 on)\n"
         "  -A <APD>    APD voltage (39V/30V)\n"
         "  -C <ch>     OTDR channel (1310/1550/1625)\n"
         "  -w <power>  specific laser power\n"
         // "  -f <freq>   OTDR sample frequency (800mhz)\n"
         // "  -add <add>  one frame (32000 sample) add count (10000)\n"
         // "  -l <loop>   loop send FPGA test\n"
         // "  -L <laser>  communicate laser\n"

    );
    exit(1);
}

static void parse_opts(int argc, char *argv[])
{
    int ret;
    while (1) {
	static const struct option lopts[] = {
	    { "device", 1, 0, 'D' },
	    { "speed", 1, 0, 's' },
	    { "red", 1, 0, 'r' },
	    { "amp", 1, 0, 'a' },
	    { "APD", 1, 0, 'A' },
	    { "freq", 1, 0, 'f' },
	    { "pluse", 1, 0, 'p' },
	    { "add", 1, 0, 'c' },
	    { "Param", 1, 0, 'P' },    // 测试参数
	    { "Laser", 0, 0, 'L' },    // 测试参数
	    { "Channel", 1, 0, 'C' },  // 测试参数
	    { "power", 1, 0, 'w' },    // 测试参数
	    { "loop", 0, 0, 'l' },     // 测试参数
	    { "test", 0, 0, 't' },     // 测试参数

	    { NULL, 0, 0, 0 },
	};
	int c;

	c = getopt_long(argc, argv, "D:s:r:a:A:f:P:L:C:w:lt", lopts, NULL);

	if (c == -1) {
	    break;
	}

	switch (c) {
	case 'D':
	    device = optarg;
	    break;
	case 's':
	    speed = atoi(optarg);
	    break;

	case 'r':
	    switch (optarg[0]) {
	    case '0':  // -r 0 关闭红光
		red = OTDR_RL_OFF;
		break;
	    case '1':  // -r 1 红光常亮
		red = OTDR_RL_ON;
		break;
	    case 's':  // -r s 慢速闪烁
		red = OTDR_RL_SLOW;
		break;
	    case 'f':  // -r f 快速闪烁
		red = OTDR_RL_FAST;
		break;
	    }
	    is_do_red = 1;
	    break;
	case 'a':
	    // 取出有效值
	    sscanf(optarg, "%x", &tmp_amp);
	    tmp_amp &= 0x34;
	    is_do_amp = 1;
	    break;
	case 'A':
	    apd = atoi(optarg);
	    if (0 == strcmp(optarg, "30")) {
		apd = TP_APD_30V;
	    }
	    else if (0 == strcmp(optarg, "39")) {
		apd = TP_APD_39V;
	    }
	    is_do_apd = 1;
	    break;
	case 'P':
	    ret = sscanf(optarg, "%[^/]/%[^/]/%[^/]", str_dist, str_pluse, str_sec);
	    if (ret != 3) {
		printf("param unmatch :\n-P <distance/pluse/time>\n");
		exit(1);
		break;
	    }
	    is_do_otdr = 1;
	    break;
	case 'f':
	    strncpy(str_freq, optarg, STRLEN);
	    break;
	case 'l':
	    is_do_loop = 1;
	    break;
	case 'w':
	    laser_power       = atoi(optarg);
	    is_do_laser_power = 1;
	    break;
	case 'L':
	    is_do_comlaser = 1;
	    break;
	case 'C':
	    channel = atoi(optarg);
	    break;
	case 't':
	    is_do_otdr = 1;
	    break;
	default:
	    print_usage(argv[0]);
	    break;
	}
    }
}
static void print_configure()
{
    printf("Device:       %s\n", device);

    char redmod[][5] = {
	"OFF",
	"ON",
	"SLOW",
	"FAST",
    };
    printf("Redlight      %s\n", (char *)(redmod + red));
    printf("\nOTDR:\n");
    printf("\t\tchannel   %d\n", channel);
    printf("\t\tParam     %s/%s/%s\n", str_dist, str_pluse, str_sec);
    // printf("\t\tFreq      %s\n", str_freq);
    printf("\t\tAMP       %d on ", tmp_amp >> 4);
    if (tmp_amp & 0x04) {
	printf("S4 on\n");
    }
    else {
	printf("S4 off\n");
    }
    printf("\t\tAPD       %dV\n", apd);
    printf("\t\tLP        %d\n", laser_power);
    printf("\t\tcount     %s\n", str_addcount);
    // printf("Speed:\t\t    %d\n\n", speed);
}


/******************************************************************************
 * 数据合法性检查
 ******************************************************************************/
// 某量程下许可的脉宽
struct list_plus
{
    char *desc;
    int   val;
};

// 许可的量程
struct list_dist
{
    char             *desc;
    int               freq;  // 测试距离与fpga采样频率关联
    struct list_plus *plus;  // 脉宽的计算与当前采样频率关联
};

struct list_plus _300m_plus[] = {
    // TP_FREQ_300M
    { "5ns", TP_PULSE_300M_5_NS },
    { "10ns", TP_PULSE_300M_10_NS },
    { "20ns", TP_PULSE_300M_20_NS },
    { "40ns", TP_PULSE_300M_40_NS },
    { (char *)NULL, (int)NULL },
};
struct list_plus _1km_plus[] = {
    // TP_FREQ_1KM
    { "5ns", TP_PULSE_1KM_5_NS },
    { "10ns", TP_PULSE_1KM_10_NS },
    { "20ns", TP_PULSE_1KM_20_NS },
    { "40ns", TP_PULSE_1KM_40_NS },
    { (char *)NULL, (int)NULL },
};
struct list_plus _5km_plus[] = {
    // TP_FREQ_5KM
    { "10ns", TP_PULSE_5KM_10_NS },
    { "20ns", TP_PULSE_5KM_20_NS },
    { "40ns", TP_PULSE_5KM_40_NS },
    { "80ns", TP_PULSE_5KM_80_NS },
    { "160ns", TP_PULSE_5KM_160_NS },
    { (char *)NULL, (int)NULL },
};

struct list_plus _10km_plus[] = {
    { "5ns", TP_PULSE_10KM_5_NS },
    { "10ns", TP_PULSE_10KM_10_NS },
    { "20ns", TP_PULSE_10KM_20_NS },
    { "40ns", TP_PULSE_10KM_40_NS },
    { "80ns", TP_PULSE_10KM_80_NS },
    { "160ns", TP_PULSE_10KM_160_NS },
    { "320ns", TP_PULSE_10KM_320_NS },
    { (char *)NULL, (int)NULL },
};

struct list_plus _30km_plus[] = {
    { "5ns", TP_PULSE_30KM_5_NS },
    { "10ns", TP_PULSE_30KM_10_NS },
    { "20ns", TP_PULSE_30KM_20_NS },
    { "40ns", TP_PULSE_30KM_40_NS },
    { "80ns", TP_PULSE_30KM_80_NS },
    { "160ns", TP_PULSE_30KM_160_NS },
    { "320ns", TP_PULSE_30KM_320_NS },
    { "640ns", TP_PULSE_30KM_640_NS },
    { (char *)NULL, (int)NULL },
};
struct list_plus _60km_plus[] = {
    { "40ns", TP_PULSE_60KM_40_NS },
    { "80ns", TP_PULSE_60KM_80_NS },
    { "160ns", TP_PULSE_60KM_160_NS },
    { "320ns", TP_PULSE_60KM_320_NS },
    { "640ns", TP_PULSE_60KM_640_NS },
    { "1280ns", TP_PULSE_60KM_1280_NS },
    { (char *)NULL, (int)NULL },
};
struct list_plus _100km_plus[] = {
    { "160ns", TP_PULSE_100KM_160_NS },
    { "320ns", TP_PULSE_100KM_320_NS },
    { "640ns", TP_PULSE_100KM_640_NS },
    { "1280ns", TP_PULSE_100KM_1280_NS },
    { "2560ns", TP_PULSE_100KM_2560_NS },
    { "5120ns", TP_PULSE_100KM_5120_NS },
    { "10240ns", TP_PULSE_100KM_10240_NS },
    { "20480ns", TP_PULSE_100KM_20480_NS },
    { (char *)NULL, (int)NULL },
};

struct list_plus _180km_plus[] = {
    { "160ns", TP_PULSE_180KM_160_NS },
    { "320ns", TP_PULSE_180KM_320_NS },
    { "640ns", TP_PULSE_180KM_640_NS },
    { "1280ns", TP_PULSE_180KM_1280_NS },
    { "2560ns", TP_PULSE_180KM_2560_NS },
    { "5120ns", TP_PULSE_180KM_5120_NS },
    { "10240ns", TP_PULSE_180KM_10240_NS },
    { "20480ns", TP_PULSE_180KM_20480_NS },
    { (char *)NULL, (int)NULL },
};

struct list_dist _dist[] = {
    { "300m", TP_FREQ_300M, _300m_plus },
    { "1km", TP_FREQ_1KM, _1km_plus },
    { "5km", TP_FREQ_5KM, _5km_plus },
    { "10km", TP_FREQ_10KM, _10km_plus },
    { "30km", TP_FREQ_30KM, _30km_plus },
    { "60km", TP_FREQ_60KM, _60km_plus },
    { "100km", TP_FREQ_100KM, _100km_plus },
    { "180km", TP_FREQ_180KM, _180km_plus },
    { (char *)NULL, (int)NULL, (struct list_plus *)NULL },
};
// 检查脉宽与量程之间的关系
// 语法  100km/160ns/15s
static int check_disc_pluse()
{
    struct list_dist *tdist = _dist;
    struct list_plus *tplus;


    while (tdist->desc) {
	// printf("desc %s\n", tdist->desc);
	if (0 == strcmp(str_dist, tdist->desc)) {
	    break;
	}
	tdist++;
    }
    if (tdist->desc == NULL) {
	printf("unspport distance %s\n", str_dist);
	// 列出所有支持的量程
	printf("Support distance is that:\n");
	tdist = _dist;
	while (tdist->desc) {
	    printf("\t%s\n", tdist->desc);
	    tdist++;
	}

	return -1;
    }
    freq = tdist->freq;


    tplus = tdist->plus;
    while (tplus->desc) {
	// printf("\tpluse %s\n", tplus->desc);
	if (0 == strcmp(str_pluse, tplus->desc)) {
	    plus = tplus->val;  // 获取脉冲值
	    break;
	}
	tplus++;
    }
    if (tplus->desc == NULL) {
	printf("unspport pluse %s\n", str_pluse);
	// 列出该量程下所有脉宽
	printf("Support plus is that:\n");
	tplus = tdist->plus;
	while (tplus->desc) {
	    printf("\tpluse %s\n", tplus->desc);
	    tplus++;
	}

	return -1;
    }
    plus = tplus->val;
    return 0;
}


struct list_freq
{
    char *desc;
    int   val;
};
struct list_freq _freq[] = {
    { "800mhz", TP_FREQ_800 },
    { "400mhz", TP_FREQ_400 },
    { "200mhz", TP_FREQ_200 },
    { "100mhz", TP_FREQ_100 },
    { "50mhz", TP_FREQ_50 },
    { "25mhz", TP_FREQ_25 },
    { "12mhz", TP_FREQ_12 },
    { "6mhz", TP_FREQ_6 },
    { (char *)NULL, (int)NULL },
};
static int check_freq()
{
    struct list_freq *tfreq = _freq;


    while (tfreq->desc) {
	// printf("desc %s\n", tdist->desc);
	if (0 == strcmp(str_freq, tfreq->desc)) {
	    break;
	}
	tfreq++;
    }

    if (tfreq->desc == NULL) {
	printf("unspport frequency%s\n", str_freq);
	// 列出该量程下所有脉宽
	printf("Support frequency is that:\n");
	tfreq = _freq;
	while (tfreq->desc) {
	    printf("\tfrequency %s\n", tfreq->desc);
	    tfreq++;
	}
	return -1;
    }
    return 0;
}


struct list_time
{
    char *desc;
    int   val;
};
struct list_time _time[] = {
    { "8s", 8 },
    { "15s", 15 },
    { "30s", 30 },
    { "1min", 60 },
    { "2min", 120 },
    { "3min", 180 },
    { (char *)NULL, (int)NULL },
};
static int check_sec()
{
    struct list_time *ttime = _time;


    while (ttime->desc) {
	// printf("desc %s\n", tdist->desc);
	if (0 == strcmp(str_sec, ttime->desc)) {
	    break;
	}
	ttime++;
    }

    if (ttime->desc == NULL) {
	printf("unspport test time%s\n", str_freq);
	// 列出该量程下所有脉宽
	printf("Support test time is that:\n");
	ttime = _time;
	while (ttime->desc) {
	    printf("\ttime %s\n", ttime->desc);
	    ttime++;
	}
	return -1;
    }
    return 0;
}

static int check_amp()
{
    if (0 == (tmp_amp & 0x34)) {
	return -1;
    }
    amp = tmp_amp;
    return 0;
}
static int check_channel()
{
    if (channel != 1310 && channel != 1550 && channel != 1625) {
	printf("unspport channel %d\n", channel);
	printf("Support channel is that:\n\t1310\n\t1550\n\t1625\n");
	return -1;
    }
    return 0;
}
static int check_apd()
{
    if (apd != TP_APD_30V && apd != TP_APD_39V) {
	printf("unspport APD %d\n", apd);
	printf("Support APD is that:\n\t30\n\t39\n");
	return -1;
    }
    return 0;
}

static int check_laser_power()
{
    if (!(OTDR_LASER_PW_LOW <= laser_power && laser_power <= OTDR_LASER_PW_HIGH)) {
	printf("unspport laser power %d\n", laser_power);
	printf("Support laser power is that:\n\t1~4\n");
	return -1;
    }
    return 0;
}


/******************************************************************************
 * 最终处理
 ******************************************************************************/
void build_otdr(struct fpga_test_param *param, int ch, int pluse, int freq, int amp1, int amp2, int apd, int power_lv)
{
    ///< 演示如何使用 @ref 构建默认测试参数
#if 0
	AUTO_BUILD_300M(param);
	AUTO_BUILD_1KM(param);
	AUTO_BUILD_5KM(param);
	AUTO_BUILD_10KM(param);
	AUTO_BUILD_60KM(param);
	AUTO_BUILD_100KM(param);
	AUTO_BUILD_180KM(param);
#endif

    param->laser    = ch;
    param->pulse    = pluse;
    param->freq     = freq;
    param->adopt_pt = 32000;
    param->add_cnt  = 10000;
    param->amp      = TP_AMP(amp1, amp2);
    param->apd_vol  = apd;
    param->power    = power_lv;
    param->isrun    = 1;
}
void do_test()
{
    struct fpga_test_param param;
    build_otdr(&param,
               channel,
               plus,
               freq,
               sw1_3[tmp_amp >> 4],
               sw4[tmp_amp & 0x04],
               apd,
               laser_power);
    printf("Run OTDR %d sec\n", sec);
    printf("Press Ctrl+C Canel\n");
    while (sec--) {
	fpga_request_test(dev, &param);
	sleep(1);
    }
}

int do_otdr()
{
    if (is_do_otdr == 0) {
	return 0;
    }
    printf("******** control otdr test ********\n");
    do_test();
    return 0;
}
int do_comlaser()
{
    int   ret;
    char *result;
    printf("******** control communicate laser checking ********\n");
    fpga_com_laser(dev, &ret);
    switch (ret) {
    case 2:
	result = "ok";
	break;
    case 1:
	result = "discover";
	break;
    default:
	result = "(unknow)";
	break;
    }
    printf("checking result %s\n", result);
    return 0;
}

int do_amp()
{
    if (is_do_amp == 0 || is_do_otdr == 1) {
	return 0;
    }
    printf("******** control amplifier ********\n");

    do_test();
    return 0;
}

int do_red()
{
    if (is_do_red == 0) {
	return 0;
    }
    printf("******** control red light ********\n");
    fpga_red_laser(dev, red);
    return 0;
}

int do_power()
{
    if (is_do_laser_power == 0 || is_do_otdr == 1) {
	return 0;
    }
    printf("******** control laser power ********\n");
    do_test();
    return 0;
}

int main(int argc, char *argv[])
{
    int ret = 0;

    // 读取输入选项
    parse_opts(argc, argv);

    // 检查并转换参数
    if (-1 == check_disc_pluse()) {
	return -1;
    }
    if (-1 == check_freq()) {
	return -1;
    }
    if (-1 == check_sec()) {
	return -1;
    }
    if (-1 == check_channel()) {
	return -1;
    }
    if (-1 == check_apd()) {
	return -1;
    }
    if (-1 == check_laser_power()) {
	return -1;
    }
    if (-1 == check_amp()) {
	return -1;
    }
    // 显示当前配置
    print_configure();

    ret = fpga_open(device, &dev, 5000000);
    switch (ret) {
    case 0:
	printf("open device success!\n");
	break;
    case -1:
	printf("bus error\n");
	break;
    case -2:
	printf("unknow device ID\n");
	return -1;
    }

    // 执行指令
    do_red();
    do_power();
    do_comlaser();
    do_otdr();

    fpga_close(dev);
    return 0;
}

#ifdef PRJ_NAME
MODULE_PROJECT(PRJ_NAME);
#endif
#if defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
MODULE_VERSION(PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif

#ifdef BUILD_DATE
MODULE_BUILD(BUILD_DATE);
#endif


MODULE_AUTHOR("MenglongWu <MenglongWoo@aliyun.com>");
MODULE_DESCRIPTION("otdr fpga-20c Ver.2015 test tool");
MODULE_DEPEND("spi_mxs.ko spidev.ko");
